qx.Class.define("lastfmbackup.test.model.Storage", {
  
  extend : qx.dev.unit.TestCase,

  members : {
    
    testee     : null,
    _timestamp : null,
    
    
    _getTestRows : function()
    {
      return lastfmbackup.test.mock.RecentTracksModel.data.slice(0, 4).reverse();
    },
    
    _generateTestRows : function(length)
    {
      var random = function(min, max)
      {
        var l = max === undefined ? min : max;
        var r = max === undefined ? 0   : min;

        return l + Math.round(Math.random() * (r - l));
      };
      var uuid   = function()
      {
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) 
        {
          var r = Math.random() * 16 | 0;
          var v = c == "x" ? r : (r & 0x3 | 0x8);
          return v.toString(16);
        });
      };
      var name   = function(min, max)
      {
        var length = random(min, max);
        var result = '';
        var s;
        while(result.length < length)
        {
          s       = Math.random().toString(36).substr(2); 
          result += (result.length + s.length > length) ? s.substr(0, length - result.length) : s; 
        }
        
        return result; 
      };
      var pair   = function(length)
      {
        return Array.range(length).map(function()
        {
          return [name(4, 16), uuid()];
        });
      };

      var artists = pair(Math.floor(length / 166));
      var albums  = pair(Math.floor(length / 83));
      var tracks  = pair(Math.floor(length / 11));
      
      var artist, album, track, date;
      var data = [];
      for(var i = 0; i < length; i++)
      {
        date   = this._timestamp++;
        artist = artists[random(artists.length - 1)];
        album  = albums[random(albums.length - 1)];
        track  = tracks[random(tracks.length - 1)];
        
        data.push([date, track[0], artist[0], album[0], track[1], artist[1], album[1]]);
      }
      
      return data;
    },
    
    setUp : function()
    {
      this._timestamp = 1221220000;
      
      this.testee = new lastfmbackup.model.Storage();
      
      this.testee.setUsername("somebody");
      this.testee.resetData();
    },
    
    tearDown : function()
    {
      this.testee.dispose();
    },    
    
    testVersionCompare : function()
    {
      this.assertException((function()
      {
        this.testee.constructor.versionCompare();
      }).bind(this), Error, "Invalid versions to compare");
      
      this.assertEquals(-1, this.testee.constructor.versionCompare("0.2.5", "0.2.5.1"));
      this.assertEquals(-1, this.testee.constructor.versionCompare("6.5.3.9", "6.5.3.19"));
      this.assertEquals(0, this.testee.constructor.versionCompare("11.22.44", "11.22.44.0.0"));
      this.assertEquals(0, this.testee.constructor.versionCompare("1.5.9", "1.5.9"));
      this.assertEquals(1, this.testee.constructor.versionCompare("6.1.2.3", "6.1.2"));
      this.assertEquals(1, this.testee.constructor.versionCompare("6", "5"));
    },
    
    testConvertToMap : function()
    {
      this.assertJsonEquals(
        this._getTestRows()
          .map(this.testee.constructor.convertToMap)
          .map(this.testee.constructor.convertToRow), 
        this._getTestRows()
      );
    },
    
    testConvertToRow : function()
    {
      this.assertJsonEquals(
        this._getTestRows()
          .map(this.testee.constructor.convertToMap)
          .map(this.testee.constructor.convertToRow), 
        this._getTestRows()
      );
    },
    
    testFetchJsonBlobEmpty : function()
    {
      this.testee.setUsername("nobody");
      this.testee.addListenerOnce("fetch", function(event)
      {
        this.resume(function()
        {
          this.assertNull(event.getData());
        }, this);
      }, this);
      this.testee.fetchJsonBlob();
      this.wait();
    },
    
    testFetchJsonBlob : function()
    {
      this.testee.addListenerOnce("save", function()
      {
        this.testee.addListenerOnce("fetch", function(event)
        {
          var reader = new window.FileReader();
          reader.onload = (function() 
          {
            this.resume(function()
            {
              this.assertJsonEquals(this._getTestRows(), qx.lang.Json.parse(reader.result));
            }, this);
          }).bind(this);
          reader.readAsText(event.getData());
        }, this);
        this.testee.fetchJsonBlob();
      }, this);
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testFetchCsvBlobEmpty : function()
    {
      this.testee.setUsername("nobody");
      this.testee.addListenerOnce("fetch", function(event)
      {
        this.resume(function()
        {
          this.assertNull(event.getData());
        }, this);
      }, this);
      this.testee.fetchCsvBlob();
      this.wait();
    },
    
    testFetchCsvBlob : function()
    {
      var expected = [
        "1358959513\tThe Dreaming\tJurojin\tThe Living Measure Of Time\t" +
          "589006c8-b6b2-460c-aa2c-bcd48849307f\t" +
          "bcde4cf4-8f13-4e55-b867-c9eeeab77015\t3616c7ae-3a27-44c6-8499-0eed4f91cfe0",
        "1359109862\tCanvas\tSoen\tCognitive\t40ad5ea4-4d0a-4765-afa0-5ce9c2adc4c1\t" +
          "7a42f70d-7c01-46d2-bd88-738967a631f0\tf49a475d-215f-4f63-a515-3bc3aab25874",
        "1359132220\tArtifact\tBalmorhea\tStranger\t11f72b3e-129a-4d77-a338-d68bd2221dd9\t" +
          "1507b368-e660-4c15-af94-3f5603561045\t46ad1d93-0492-45fd-9b48-c385d50b6201",
        "1359209542\tIthica 27 ϕ 9\tMogwai\tTen Rapid\t" +
          "fc526a84-081b-4491-b598-4c93ec14b0f5\tcee3f961-4197-483b-a3ad-73242a97eb4a"
      ].join("\n");
      
      this.testee.addListenerOnce("save", function()
      {
        this.testee.addListenerOnce("fetch", function(event)
        {
          var reader = new window.FileReader();
          reader.onload = (function() 
          {
            this.resume(function()
            {
              this.assertJsonEquals(expected, reader.result.trim());
            }, this);
          }).bind(this);
          reader.readAsText(event.getData());
        }, this);
        this.testee.fetchCsvBlob();
      }, this);
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testFetchDataEmpty : function()
    {
      this.testee.setUsername("nobody");
      this.testee.addListenerOnce("fetch", function(event)
      {
        this.resume(function()
        {
          this.assertNull(event.getData());
        }, this);
      }, this);
      this.testee.fetchData();
      this.wait();
    },
    
    testFetchData : function()
    {
      this.testee.addListenerOnce("save", function()
      {
        var count = 0;
        var onFetchId = this.testee.addListener("fetch", function(event)
        {
          this.assertJsonEquals(this._getTestRows(), event.getData());
          
          count++;
          if(count > 8)
          {
            this.resume(function()
            {
              this.testee.removeListenerById(onFetchId);
            }, this);
          }
          else
          {
            this.testee.fetchData();
          }
        }, this);
        this.testee.fetchData();
      }, this);
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testGetStats : function()
    {
      this.testee.setUsername("nobody");
      this.assertNull(this.testee.getStats());
      
      this.testee.addListenerOnce("save", function()
      {
        this.resume(function()
        {
          var actual = this.testee.getStats();
  
          this.assert(Date.now() - actual.lastRun < 100);
          delete actual.lastRun;
          
          this.assertJsonEquals({
            "firstScrobble"     : 1358959513000,
            "lastScrobble"      : 1359209542000,
            "scrobbles"         : 4,
            "storageUsed"       : 648,
            "formatVersion"     : qx.core.Environment.get("lastfmbackup.version"),
            "rowsetChunkLength" : 1
          }, actual);
        }, this);
      }, this);
      this.testee.setUsername("somebody");
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testSaveData : function()
    {
      this.testee.addListenerOnce("save", function()
      {
        this.testee.addListenerOnce("fetch", function(event)
        {
          this.assertJsonEquals(this._getTestRows(), event.getData());
          
          this.testee.resetData();
          var data = [[1358780486, "Imbue Élan Vital", "Ceterum", "Fathom", null, null, null]];
          
          this.testee.addListenerOnce("save", function()
          {
            this.testee.addListenerOnce("fetch", function(event)
            {
              this.resume(function()
              {
                this.assertJsonEquals(data, event.getData());
              }, this);
            }, this);
            this.testee.fetchData();
          }, this);
          this.testee.saveData(data);
        }, this);
        this.testee.fetchData();
      }, this);
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testSaveDataPerformance : function()
    {
      this.testee.setChunkLength(2048);
      
      var start = Date.now();
      
      var data = Array.range(4).map(function()
      {
        return this._generateTestRows(5000);
      }, this);
      
      var dataAdd = data.concat();
      this.testee.addListener("save", function()
      {
        if(dataAdd.length)
        {
          this.testee.saveData(dataAdd.shift());
        }
        else
        {
          this.testee.removeListener("save", arguments.callee, this);
          
          var actual = this.testee.getStats();
          
          this.assert(Date.now() - start < 12 * 1000, "overall performance");
          this.assert(Date.now() - actual.lastRun < 100);
          delete actual.lastRun;
          
          this.assert(actual.storageUsed > 190 * 1000 && actual.storageUsed < 200 * 1000);
          delete actual.storageUsed;
          
          var expected = {
            "firstScrobble"     : 1221220000000,
            "lastScrobble"      : 1221239999000,
            "scrobbles"         : 20000,
            "formatVersion"     : qx.core.Environment.get("lastfmbackup.version"),
            "rowsetChunkLength" : 12
          };
          this.assertJsonEquals(expected, actual);
          
          this.testee.addListenerOnce("fetch", function(event)
          {
            this.resume(function()
            {
              this.assertJsonEquals(data.reduce(function(a, b)
              {
                return a.concat(b); 
              }), event.getData());
            }, this);
          }, this);
          this.testee.fetchData();
        }
      }, this);
      
      var expected = Array.range(7).map(function(i)
      {
        return  i / 6;
      });
      expected     = [].concat(expected, expected, expected, expected);
      this.testee.addListener("changeSaveProgress", function(event)
      {
        this.assertEquals(expected.shift(), event.getData());
      }, this);
      
      this.testee.saveData(dataAdd.shift());
      this.wait(16000);
    },
    
    testResetData : function()
    {
      this.testee.addListenerOnce("save", function()
      {
        this.testee.addListenerOnce("fetch", function(event)
        {
          this.assertJsonEquals(this._getTestRows(), event.getData());
          
          this.testee.resetData();

          this.testee.addListenerOnce("fetch", function(event)
          {
            this.resume(function()
            {
              this.assertNull(event.getData());
              this.assertNull(this.testee.getStats());
            }, this);
          }, this);
          this.testee.fetchData();
        }, this);
        this.testee.fetchData();
      }, this);
      this.testee.setUsername("somebody");
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testCompatibilityReset : function()
    {
      this.testee.addListenerOnce("save", function()
      {
        this.resume(function()
        {
          this.testee.setUsername("somebody2");
          this.testee.setUsername("somebody");
          this.assertEquals(4, this.testee.getStats().scrobbles);
          
          this.testee.setUsername("somebody2");
          this.testee._minCompatibleWith = 0.4;
          this.testee.setUsername("somebody");
          this.assertNull(this.testee.getStats());
        }, this);
      }, this);
      this.testee.saveData(this._getTestRows());
      this.wait();
    },
    
    testAppendFlush : function()
    {
      var data = [
        {
          "date"  : 1358780486, 
          "track" : {
            "name" : "Imbue Élan Vital",
            "mbid" : null
          },
          "album" : {
            "name" : "Fathom",
            "mbid" : null
          },
          "artist" : {
            "name" : "Ceterum",
            "mbid" : null
          }
        },
        // dup
        {
          "date"  : 1358959513, 
          "track" : {
            "name" : "The Dreaming",
            "mbid" : "589006c8-b6b2-460c-aa2c-bcd48849307f"
          },
          "album" : {
            "name" : "The Living Measure Of Time",
            "mbid" : "3616c7ae-3a27-44c6-8499-0eed4f91cfe0"
          },
          "artist" : {
            "name" : "Jurojin",
            "mbid" : "bcde4cf4-8f13-4e55-b867-c9eeeab77015"
          }
        }
      ];
      
      /**
       * scenario:
       *   - set data
       *   - assert data1
       *   - append data
       *   - assert data1
       *   - flush data
       *   - wait until saved
       *   - assert data2
       */
      this.testee.addListenerOnce("save", function()
      {
        this.testee.addListenerOnce("fetch", function(event)
        {
          this.assertJsonEquals(this._getTestRows(), event.getData());
          
          this.testee.addListenerOnce("save", function()
          {
            this.testee.addListenerOnce("fetch", function(event)
            {
              this.resume(function()
              {
                this.assertJsonEquals(
                  [this.testee.constructor.convertToRow(data[0])].concat(this._getTestRows()), 
                  event.getData()
                );
              }, this);
            }, this);
            this.testee.fetchData();            
          }, this);
          this.testee.flushData();
        }, this);
        this.testee.appendData(data);
        this.testee.fetchData();
      }, this);
      this.testee.saveData(this._getTestRows());
      this.wait();
    }

  }
  
});